// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;

namespace SnapObjects.Data
{
    internal class DataTableModelEntryLazyReader<TModel> : IEnumerable<IModelEntry<TModel>>
    {
        private readonly bool _changedOnly;
        private readonly DataFormatSetting _setting;
        private readonly DataRowCollection _rows;
        private readonly DataColumnCollection _columns;

        public DataTableModelEntryLazyReader(
            DataRowCollection rows,
            DataColumnCollection columns,
            bool changedOnly = false,
            DataFormatSetting setting = null)
        {
            FormatUtils.CheckSetting(setting, null);

            _rows = rows;
            _columns = columns;
            _setting = setting ?? new DataFormatSetting();
            _changedOnly = changedOnly;
        }

        public IEnumerator<IModelEntry<TModel>> GetEnumerator()
        {
            for (var index = 0; index < _rows.Count; index++)
            {
                if (this.FilterRow(index))
                {
                    var row = _rows[index];

                    var state = this.GetModelState(row);

                    var isChanged =
                        state == ModelState.NewModified ||
                        state == ModelState.Modified ||
                        state == ModelState.Deleted;

                    if (!_changedOnly || (_changedOnly && isChanged))
                    {
                        yield return this.GetModelEntry(row, state);
                    }
                }
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        private IModelEntry<TModel> GetModelEntry(DataRow row, ModelState state)
        {
            var entry = new ModelEntry<TModel>(state);

            for (var index = 0; index < _columns.Count; index++)
            {
                if (!this.FilterColumn(index))
                {
                    continue;
                }

                var column = _columns[index];

                object current = DBNull.Value;
                object original = DBNull.Value;

                switch (row.RowState)
                {
                    case DataRowState.Added:
                        current = row[column, DataRowVersion.Default];
                        break;
                    case DataRowState.Modified:
                        current = row[column, DataRowVersion.Default];
                        original = row[column, DataRowVersion.Original];
                        break;
                    case DataRowState.Deleted:
                        current = original = row[column, DataRowVersion.Original];
                        break;
                    case DataRowState.Unchanged:
                        current = original = row[column, DataRowVersion.Current];
                        break;
                }

                var propState = !current.Equals(original) ? PropertyState.Modified : PropertyState.NotModified;

                entry.SetPropertyEntry(column.ColumnName, current, original, propState);
            }

            return entry;
        }

        private ModelState GetModelState(DataRow row)
        {
            var state = ModelState.NotTracked;

            switch (row.RowState)
            {
                case DataRowState.Added: state = ModelState.NewModified; break;
                case DataRowState.Deleted: state = ModelState.Deleted; break;
                case DataRowState.Modified: state = ModelState.Modified; break;
                case DataRowState.Unchanged: state = ModelState.NotModified; break;
                default: state = ModelState.NotTracked; break;
            }

            return state;
        }

        private bool FilterRow(int index)
        {
            return index >= _setting.StartRow && (_setting.EndRow < 0 || index <= _setting.EndRow);
        }

        private bool FilterColumn(int index)
        {
            return index >= _setting.StartProperty &&
                (_setting.EndProperty < 0 || index <= _setting.EndProperty);
        }
    }
}
